/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.stdlib;

import fr.x9c.cadmium.kernel.AbstractCodeRunner;
import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;

/**
 * Implements all primitives from 'obj.c'.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.2
 * @since 1.0
 */
@PrimitiveProvider
public final class Obj {

    /** Special tag returned for long value. */
    private static final Value LONG_TAG = Value.createFromLong(1000);

    /** Special tag returned for non-block value. */
    private static final Value NON_BLOCK_TAG = Value.createFromLong(1001);

    /** Special tag returned for non-aligned value. */
    private static final Value UNALIGNED_TAG = Value.createFromLong(1002);

    /**
     * No instance of this class.
     */
    private Obj() {
    } // end empty constructor

    /**
     * Constructs a string.
     * @param ctxt context
     * @param size string size
     * @return a string of size <tt>size</tt>,
     *         with all its bytes initialized to zero
     */
    @Primitive
    public static Value caml_static_alloc(final CodeRunner ctxt,
                                          final Value size) {
        return Value.createFromBlock(Block.createString(size.asLong()));
    } // end method 'caml_static_alloc(CodeRunner, Value)'

    /**
     * Does nothing, as will be handled by JVM.
     * @param ctxt context
     * @param blk ignored
     * @return <i>unit</i>
     */
    @Primitive
    public static Value caml_static_free(final CodeRunner ctxt,
                                         final Value blk) {
        return Value.UNIT;
    } // end method 'caml_static_free(CodeRunner, Value)'

    /**
     * Does nothing.
     * @param ctxt context
     * @param blk ignored
     * @param size ignored
     * @return <i>unit</i>
     * @throws Fail.Exception if the code runner is native
     */
    @Primitive
    public static Value caml_static_release_bytecode(final CodeRunner ctxt,
                                                     final Value blk,
                                                     final Value size)
        throws Fail.Exception {
        if (ctxt.isNative()) {
            Fail.failWith("Meta.static_release_bytecode impossible with native code");
        } // end if
        return Value.UNIT;
    } // end method 'caml_static_release_bytecode(CodeRunner, Value, Value)'

    /**
     * Constructs a string from another one.
     * @param ctxt context
     * @param blk string to copy
     * @param size size for created string
     * @return a string of length <tt>size</tt>, with its bytes initialized
     *         from <tt>blk</tt>, as mush as possible
     */
    @Primitive
    public static Value caml_static_resize(final CodeRunner ctxt,
                                           final Value blk,
                                           final Value size) {
        final int sz = size.asLong();
        final Block res = Block.createString(sz);
        final byte[] b = blk.asBlock().getBytes();
        System.arraycopy(b, 0, res.getBytes(), 0, Math.min(b.length, sz));
        return Value.createFromBlock(res);
    } // end method 'caml_static_resize(CodeRunner, Value, Value)'

    /**
     * Tests whether a value is a block.
     * @param ctxt context
     * @param arg value to test
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if <tt>arg</tt> is a
     *         block value, {@link fr.x9c.cadmium.kernel.Value#FALSE} otherwise
     */
    @Primitive
    public static Value caml_obj_is_block(final CodeRunner ctxt,
                                          final Value arg) {
        return arg.isBlock() ? Value.TRUE : Value.FALSE;
    } // end method 'caml_obj_is_block(CodeRunner, Value)'

    /**
     * Returns the tag of a value.
     * @param ctxt context
     * @param arg value to get tag from
     * @return <tt>1000</tt> if <tt>arg</tt> is a long value, <br/>
     *         the actual tag if <tt>arg</tt> is a block, <br/>
     *         <tt>1001</tt> otherwise (<i>e.g.</i> code pointer)
     */
    @Primitive
    public static Value caml_obj_tag(final CodeRunner ctxt,
                                     final Value arg) {
        if (arg.isLong()) {
            return Obj.LONG_TAG;
        } else if (arg.isBlock()) {
            return Value.createFromLong(arg.asBlock().getTag());
        } else {
            return Obj.NON_BLOCK_TAG;
        } // end if/elsif/else
    } // end method 'caml_obj_tag(CodeRunner, Value)'

    /**
     * Changes the tag of a block.
     * @param ctxt context
     * @param arg block
     * @param newTag new tag for <tt>arg</tt>
     * @return <i>unit</i>
     */
    @Primitive
    public static Value caml_obj_set_tag(final CodeRunner ctxt,
                                         final Value arg,
                                         final Value newTag) {
        arg.asBlock().setTag(newTag.asLong());
        return Value.UNIT;
    } // end method 'caml_obj_set_tag(CodeRunner, Value, Value)'

    /**
     * Constructs a block.
     * @param ctxt context
     * @param tag block tag
     * @param size block size
     * @return a block of given size and tag, <br/>
     *         all values are initialized to <i>unit</i>
     */
    @Primitive
    public static Value caml_obj_block(final CodeRunner ctxt,
                                       final Value tag,
                                       final Value size) {
        final int tg = tag.asLong();
        final int sz = size.asLong();
        if (sz == 0) {
            return ctxt.getContext().getAtom(tg);
        } else {
            return Value.createFromBlock(Block.createBlock(sz, tg));
        } // end if/else
    } // end method 'caml_obj_block(CodeRunner, Value, Value)'

    /**
     * Duplicates a block.
     * @param ctxt context
     * @param arg block to duplicate
     * @return <tt>arg</tt> if its size is equal to zero,
     *         a copy of <tt>arg</tt> otherwise
     */
    @Primitive
    public static Value caml_obj_dup(final CodeRunner ctxt,
                                     final Value arg) {
        final int sz = arg.asBlock().getWoSize();
        if (sz == 0) {
            return arg;
        } else {
            return Value.createFromBlock(arg.asBlock().duplicate());
        } // end if/else
    } // end method 'caml_obj_dup(CodeRunner, Value)'

    /**
     * Shortens a block.
     * @param ctxt context
     * @param v block to shorten
     * @param newSize new block size
     * @return <i>unit</i>
     * @throws Fail.Exception raises <i>Invalid_argument</i> if
     *                        <tt>newSize</tt> <= 0 or > to current size
     */
    @Primitive
    public static Value caml_obj_truncate(final CodeRunner ctxt,
                                          final Value v,
                                          final Value newSize)
        throws Fail.Exception {
        v.asBlock().truncate(newSize.asLong());
        return Value.UNIT;
    } // end method 'caml_obj_truncate(CodeRunner, Value, Value)'

    /**
     * Returns the value at a given offset.
     * @param ctxt context
     * @param v block giving the base address
     * @param offset offset of returned value
     * @return the value at the passed offset, using the passed value as the base
     */
    @Primitive
    public static Value caml_obj_add_offset(final CodeRunner ctxt,
                                            final Value v,
                                            final Value offset) {
        return v.asBlock().offset(offset.asBlock().asInt32());
    } // end method 'caml_obj_add_offset(CodeRunner, Value, Value)'

    /**
     * Follows a forward value.
     * @param ctxt context
     * @param v value to follow
     * @return the value "pointed" by <tt>v</tt> if <tt>v</tt> is a forward
     *         value, <tt>v</tt> otherwise
     */
    @Primitive
    public static Value caml_lazy_follow_forward(final CodeRunner ctxt,
                                                 final Value v) {
        if (v.isBlock() && (v.asBlock().getTag() == Block.FORWARD_TAG)) {
            return v.asBlock().get(0);
        } else {
            return v;
        } // end if/else
    } // end method 'caml_lazy_follow_forward(CodeRunner, Value)'

    /**
     * Constructs a forward block.
     * @param ctxt context
     * @param v value to forward to
     * @return a forward block to <tt>v</tt>
     */
    @Primitive
    public static Value caml_lazy_make_forward(final CodeRunner ctxt,
                                               final Value v) {
        final Block b = Block.createBlock(Block.FORWARD_TAG, v);
        return Value.createFromBlock(b);
    } // end method 'caml_lazy_make_forward(CodeRunner, Value)'

    /**
     * Returns a public method from an object.
     * @param ctxt context
     * @param obj object
     * @param tag method tag
     * @return the public method of <tt>obj</tt> having tag <tt>tag</tt>
     */
    @Primitive
    public static Value caml_get_public_method(final CodeRunner ctxt,
                                               final Value obj,
                                               final Value tag) {
        return AbstractCodeRunner.getMethod(obj, tag.getRawValue());
    } // end method 'caml_get_public_method(CodeRunner, Value, Value)'

} // end class 'Obj'
